#include "src/net/connector.h"

#include <assert.h>

#include "src/base/fio66_logger.h"
#include "src/net/channel.h"
#include "src/net/event_loop.h"
#include "src/net/sockets_opt.h"

namespace fio66::net
{
  Connector::Connector(EventLoop* loop, const InetAddress& serverAddr)
  : loop_(loop),
    server_addr_(serverAddr),
    connect_(false),
    state_(kDisconnected),
    retry_delay_ms_(kInitRetryDelayMs)
{
  LOGD << "ctor[" << this << "]";
}

Connector::~Connector()
{
  LOGD << "dtor[" << this << "]";
  assert(!channel_);
}

void Connector::Start()
{
  connect_ = true;
  loop_->RunInLoop(std::bind(&Connector::StartInLoop, this)); // FIXME: unsafe
}

void Connector::StartInLoop()
{
  loop_->AssertInLoopThread();
  assert(state_ == kDisconnected);
  if (connect_)
  {
    Connect();
  }
  else
  {
    LOGD << "do not Connect";
  }
}

void Connector::Stop()
{
  connect_ = false;
  loop_->QueueInLoop(std::bind(&Connector::StopInLoop, this)); // FIXME: unsafe
  // FIXME: cancel timer
}

void Connector::StopInLoop()
{
  loop_->AssertInLoopThread();
  if (state_ == kConnecting)
  {
    SetState(kDisconnected);
    int sockfd = RemoveAndResetChannel();
    Retry(sockfd);
  }
}

void Connector::Connect()
{
  int sockfd = sockets::CreateNonblockingOrDie(server_addr_.Family());
  int ret = sockets::Connect(sockfd, server_addr_.GetSockAddr());
  int savedErrno = (ret == 0) ? 0 : errno;
  switch (savedErrno)
  {
    case 0:
    case EINPROGRESS:
    case EINTR:
    case EISCONN:
      Connecting(sockfd);
      break;

    case EAGAIN:
    case EADDRINUSE:
    case EADDRNOTAVAIL:
    case ECONNREFUSED:
    case ENETUNREACH:
      Retry(sockfd);
      break;

    case EACCES:
    case EPERM:
    case EAFNOSUPPORT:
    case EALREADY:
    case EBADF:
    case EFAULT:
    case ENOTSOCK:
      LOGE << "Connect error in Connector::StartInLoop " << savedErrno;
      sockets::Close(sockfd);
      break;

    default:
      LOGE << "Unexpected error in Connector::StartInLoop " << savedErrno;
      sockets::Close(sockfd);
      // connectErrorCallback_();
      break;
  }
}

void Connector::Restart()
{
  loop_->AssertInLoopThread();
  SetState(kDisconnected);
  retry_delay_ms_ = kInitRetryDelayMs;
  connect_ = true;
  StartInLoop();
}

void Connector::Connecting(int sockfd)
{
  SetState(kConnecting);
  assert(!channel_);
  channel_.reset(new Channel(loop_, sockfd));
  channel_->SetWriteCallback(
      std::bind(&Connector::HandleWrite, this)); // FIXME: unsafe
  channel_->SetErrorCallback(
      std::bind(&Connector::HandleError, this)); // FIXME: unsafe

  // channel_->tie(shared_from_this()); is not working,
  // as channel_ is not managed by shared_ptr
  channel_->EnableWriting();
}

int Connector::RemoveAndResetChannel()
{
  channel_->DisableAll();
  channel_->Remove();
  int sockfd = channel_->Fd();
  // Can't reset channel_ here, because we are inside Channel::handleEvent
  loop_->QueueInLoop(std::bind(&Connector::ResetChannel, this)); // FIXME: unsafe
  return sockfd;
}

void Connector::ResetChannel()
{
  channel_.reset();
}

void Connector::HandleWrite()
{
  LOGI << "Connector::HandleWrite " << state_;

  if (state_ == kConnecting)
  {
    int sockfd = RemoveAndResetChannel();
    int err = sockets::GetSocketError(sockfd);
    if (err)
    {
      LOGW << "Connector::HandleWrite - SO_ERROR = "
               << err << " " << strerror_tl(err);
      Retry(sockfd);
    }
    else if (sockets::IsSelfConnect(sockfd))
    {
      LOGW << "Connector::HandleWrite - Self Connect";
      Retry(sockfd);
    }
    else
    {
      SetState(kConnected);
      if (connect_)
      {
        new_connection_callback_(sockfd);
      }
      else
      {
        sockets::Close(sockfd);
      }
    }
  }
  else
  {
    // what happened?
    assert(state_ == kDisconnected);
  }
}

void Connector::HandleError()
{
  LOGE << "Connector::HandleError state=" << state_;
  if (state_ == kConnecting)
  {
    int sockfd = RemoveAndResetChannel();
    int err = sockets::GetSocketError(sockfd);
    LOGI << "SO_ERROR = " << err << " " << strerror_tl(err);
    Retry(sockfd);
  }
}

void Connector::Retry(int sockfd)
{
  sockets::Close(sockfd);
  SetState(kDisconnected);
  if (connect_)
  {
    LOGI << "Connector::Retry - Retry Connecting to " << server_addr_.ToIpPort()
             << " in " << retry_delay_ms_ << " milliseconds. ";
    loop_->RunAfter(retry_delay_ms_/1000.0,
                    std::bind(&Connector::StartInLoop, shared_from_this()));
    retry_delay_ms_ = std::min(retry_delay_ms_ * 2, kMaxRetryDelayMs);
  }
  else
  {
    LOGD << "do not Connect";
  }
}

} // namespace fio66::net
